package TopTen;

import TopTen.DatabaseStations;

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;

/**
 * 
 * @author jonatanprins
 * @version rc1
 */
public class TopTenGenerator implements Runnable {

	/*
	 * Declaration of variables
	 */
	/** Length of a single file in seconds 						*/
	private final static int FILETIME = 600;
	/** length of a record in bytes 							*/
	private final static int LINELENGTH = 56;
	/** Length of the temperature in bytes 						*/
	private final static int TEMPLENGTH = 5;
	/** length of the dew point in bytes						*/
	private final static int DEWPLENGTH = 5;
	/** Interval of temperature readout							*/
	private final static int INTERVAL 	= 10;
	/** Amount of countries in the continents					*/
	private final static int COUNTRIES	= 598;						
	/** Hours in a day											*/
	private final static int HOURS		= 24;
	/** Every Nth station the progress bar should be updated	*/
	private final static int UPDATEINTERVAL = ( (FILETIME / INTERVAL) 		
			* HOURS * COUNTRIES ) / 100;											

	/*
	 * Variables used by the progress bar
	 */
	private int stepper = 1;			
	private long timeElapsed = 0;
	public int progress = 0;
	public String currentProcess = "";

	/*
	 * 
	 */
	private DatabaseStations database = new DatabaseStations();

	/*
	 * Collections used for generating the top 10
	 */	
	private HashMap<String, ArrayList<Integer>> countries;
	private ArrayList<TopTenObject> topTen;
	private ArrayList<RandomAccessFile> rafs;
	private HashMap<String, ArrayList<Double>> allTemps;

	/**
	 * Constructor
	 */
	public TopTenGenerator() {
		this.database = new DatabaseStations();
		initTopTen();
		new Thread(this).start();
	}

	/**
	 * Initializes a new top 10, used before the creation of a top 10
	 */
	private void initTopTen() {
		this.rafs = new ArrayList<RandomAccessFile>();
		this.topTen = new ArrayList<TopTenObject>();
		this.progress = 0;
		this.currentProcess = "";
		this.stepper = 1;
		this.allTemps = new HashMap<String, ArrayList<Double>>();
		this.countries = new HashMap<String, ArrayList<Integer>>();

		try {
			countries = database.getStationsByCountry();
		} catch (Exception e) {
			System.out.println(e);
		}
	}

	/**
	 * Writes the top10 to a file , the date of yesterday will be the name of the file.
	 */
	private void writeTheTopTenToAFile() throws IOException {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DATE, -1);
		DateFormat dateFormat = new SimpleDateFormat("ddMMyyyy");

		String date = dateFormat.format(cal.getTime());

		String path = "Measurements/TopTen/" + date + ".csv";
		FileWriter ttWriter = new FileWriter(path);
		
		/* *********** File example *********** */
		/* sep=,								*/		
		/* 1,Netherlands,10.0					*/
		/* 2,Ghana,22.0							*/
		/* 3,Belgium,10.0						*/
		/* 4,Germany,20.0						*/
		/* 5,KennethLand,50.0					*/
		/* 6,HenriLand,-100.0					*/
		/* 7,MarkLand,100.1						*/
		/* 8,Land,40.9							*/
		/* 9,KaasLand,30.9						*/
		/* 10,Lamp,80.9							*/
		/* *********** End example  *********** */
		ttWriter.append("sep=,\n");
		for (int spot = 0; spot < topTen.size(); spot++) {
			TopTenObject obj = topTen.get(spot);
			ttWriter.append("" + (spot + 1));
			ttWriter.append(',');
			ttWriter.append(obj.COUNTRY);
			ttWriter.append(',');
			ttWriter.append("" + obj.AVERAGE);
			ttWriter.append('\n');			
		}

		ttWriter.flush();
		ttWriter.close();
	}

	/**
	 * Calculate the heat index corrected temperature with tempair and dewpoint. 
	 * This method calls upon CalcHumidity to calculate the 
	 * humidity used in the formula, the formula uses the temperature in fahrenheit, we store the temperature as celcius. 
	 * Therefore the temperature has to be changed to fahrenheit and back.
	 * @param tempair Air temperature in C
	 * @param dewpoint Dew point in C
	 * @return The Heat Index Corrected temperature
	 */
	private Double heatIndexCorrectedTemperature(Double tempair, Double dewpoint) {
		Double rh = CalcHumidity(tempair, dewpoint);
		Double T = toFahrenheit(tempair);

		/* Returning the result of the formula */
		return toCelcius(-42.379+(2.04901523*T)+(10.14333127*rh)
				-(0.22475541*T*rh)-(6.83783*Math.pow(10,-3)*Math.pow(T,2))
				-(5.481717*Math.pow(10,-2)*Math.pow(rh,2))+(1.22874*Math.pow(10,-3)*Math.pow(T,2)*rh)
				+(8.5282*Math.pow(10,-4)*T*Math.pow(rh,2))-(1.99*Math.pow(10,-6)*Math.pow(T,2)*Math.pow(rh,2)));

	}

	/**
	 * Celcius to fahrenheit
	 * @param celcius Degrees Celcius
	 * @return Degrees Fahrenheit
	 */
	private Double toFahrenheit(Double celcius) {		
		return (9.0/5.0)*celcius+32;
	}

	/***
	 * fahrenheit to celcius
	 * @param fahrenheit Degrees Fahrenheit
	 * @return Degrees Celcius
	 */
	private Double toCelcius(Double fahrenheit) {
		return (5.0/9.0)*(fahrenheit-32);
	}

	/***
	 * Calculate the relative humidity from temperature and dewpoint
	 * @param temp Temperature in C
	 * @param dewp Dew point in C
	 * @return Humidity in percentage
	 */
	private Double CalcHumidity(Double temp, Double dewp){
		Double svp = Math.pow(6.11*10.0, 7.5*temp / (237.7+temp));
		Double avp = Math.pow(6.11*10.0, 7.5*dewp / (237.7+dewp));

		return (avp/svp) * 100.0;
	}

	/**
	 * Loads all yesterday's files for the top ten day at once
	 */	
	private void loadFiles() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DATE, -1);
		DateFormat dateFormat = new SimpleDateFormat("ddMMyyyy");

		String date = dateFormat.format(cal.getTime());

		for (int hour = 0; hour < 24; hour++) {
			String path = "Measurements/" + date + "/"+ hour;
			try {
				rafs.add(new RandomAccessFile(path, "r"));
			}
			catch (FileNotFoundException fnf) {
				System.out.println("File not found exception!");
			}
		}
	}

	/**
	 * Generates the top ten from this.allTemps and stores it in a file
	 */
	private void generateTopTen() {
		for (String country : countries.keySet()) {
			ArrayList<Double> al = allTemps.get(country);

			Integer divide = al.size();
			Double sum = 0.0;

			for(Double value : al) {
				sum += value;
			}
			Double avg = (double)(Math.round(10*(sum / divide)))/10;
			TopTenObject current = new TopTenObject(country, avg);

			for (int i = 0; i < topTen.size(); i++) {
				TopTenObject tObj = topTen.get(i);
				if (current.AVERAGE > tObj.AVERAGE && !topTen.contains(current)) {
					topTen.add(i, current);
				}
			}

			if(topTen.size() < 10 && !topTen.contains(current))
				topTen.add(current);
			else
				while (topTen.size() > 10)
					topTen.remove(10);
		}

		try {
			this.writeTheTopTenToAFile();
			progress = 101;

		} catch (IOException ioe) {
			System.out.println("Failed writing the top 10 file, top 10 only stored in RAM!");
			System.out.println("Top 10:\n");
			for (int spot = 0; spot < topTen.size(); spot++) {
				TopTenObject obj = topTen.get(spot);
				System.out.print("" + (spot + 1));
				System.out.print(',');
				System.out.print(obj.COUNTRY);
				System.out.print(',');
				System.out.print("" + obj.AVERAGE);
				System.out.print('\n');			
			}
		}

	}

	public void changeElapsed() {
		this.currentProcess = (((System.currentTimeMillis() - timeElapsed) / 1000)) + " seconds elapsed" ;
	}

	/**
	 * Gets all temperatures from the files of yesterday loaded in loadFiles() and puts them in this.allTemps.
	 * It does this per file, so it starts with the first file and chronologically runs through that file, jumping
	 * from one position to the next, with the skipBytes(). Using skipBytes allows us to minimize the distance the 
	 * harddisk head has to move.
	 */
	private void getTemperatures() {
		HashMap<Integer, Integer> stations = database.getStations();		

		for (RandomAccessFile file : rafs) {
			for (String country : countries.keySet()) {

				ArrayList<Integer> cStations = countries.get(country);

				for (Integer station : cStations) {
					int index = stations.get(station);
					try {		
						int counter= 0;

						while(counter<FILETIME){
							long positionNow = counter*(LINELENGTH * 8000) + ((index*LINELENGTH));
							int bytesToJump = (int)(positionNow - file.getFilePointer());
							if (counter-INTERVAL < 0) {
								file.seek(counter*(LINELENGTH * 8000) + ((index*LINELENGTH)));
							} else {
								file.skipBytes(bytesToJump);
							}
							byte[] temp = new byte[TEMPLENGTH];
							for(int i = 0; i<TEMPLENGTH; i++){
								temp[i] = file.readByte();
							}

							byte[] dewp = new byte[DEWPLENGTH];
							for(int i = 0; i<DEWPLENGTH; i++){
								dewp[i] = file.readByte();
							}
							Double temperature = Double.parseDouble(new String(temp));
							Double dewpoint = Double.parseDouble(new String (dewp));

							stepper++;

							if (stepper >= UPDATEINTERVAL) {
								stepper = 1;
								if (this.progress < 100) {
									this.progress++;
									changeElapsed();
								}
							}

							if (allTemps.containsKey(country)) {
								allTemps.get(country).add(heatIndexCorrectedTemperature(temperature, dewpoint)); 

							} else {
								ArrayList<Double> toAdd = new ArrayList<Double>();
								toAdd.add(heatIndexCorrectedTemperature(temperature, dewpoint));
								allTemps.put(country, toAdd);
							}
							counter+=INTERVAL;
						}				
					} catch (FileNotFoundException e) {	
						System.out.println("Missing file, skipping...");
					} catch (IOException e) {
						System.out.println("RRR" + e);
					}
				}
			}
		}
	}


	/**
	 * Run method
	 */
	@Override
	public void run() {
		while (true) {
			Calendar cal = Calendar.getInstance();
			DateFormat dateFormat = new SimpleDateFormat("H");
			Integer date = Integer.parseInt(dateFormat.format(cal.getTime()));

			/* Wait for 0 (Midnight) */
			while (date != 00) {
				try {
					Thread.sleep(1800000);
				} catch (Exception e) {
					System.out.println(e);
				}
			}
			timeElapsed = System.currentTimeMillis();
			
			currentProcess = "Opening process window";
			new ProgressBar(this);
			currentProcess = "Initializing new top ten...";
			this.initTopTen();
			currentProcess = "Done initializing, Loading files...";
			this.loadFiles();
			currentProcess = "Done loading, fetching data...";
			this.getTemperatures();
			currentProcess = allTemps.size() + " countries added, generating top ten...";
			this.generateTopTen();
			currentProcess = "DONE!!";
			cal = Calendar.getInstance();
			date = Integer.parseInt(dateFormat.format(cal.getTime()));

			/* Wait for 0 to pass if it's done writing the top10 within the hour. */
			while (date < 1) {
				try {
					Thread.sleep(1800000);
				} catch (Exception e) {
					System.out.println(e);
				}
			}			
		}		
	}

	public static void main(String[] args) {
		new TopTenGenerator();
	}
}



